知识点名称:bind方式启动服务
编号: K6-4
前驱知识点编号:K6-1,K3-2
作者:刘凤华
讲义内容:
前面我们提到如果想让服务与调用者进行方法调用和数据交互时,应该使用bindService的方式启动服务,先来看bindService启动的服务的主要特点:
(1). bindService启动的服务在调用者和服务之间是典型的client-server的接口,即调用者是客户端,service是服务端,service就一个,但是连接绑定到service上面的客户端client可以是一个或多个。这里所提到的client指的是组件,比如某个Activity。
(2).客户端client(即调用bindService的一方,比如某个Activity)可以通过IBinder接口获取Service的实例,从而可以实现在client端直接调用Service中的方法以实现灵活的交互,并且可借助IBinder实现跨进程的client-server的交互,这在纯startService启动的Service中是无法实现的。
(3).不同于startService启动的服务默认无限期执行(可以通过Context的stopService或Service的stopSelf方法停止运行),bindService启动的服务的生命周期与其绑定的client息息相关。当client销毁的时候,client会自动与Service解除绑定,当然client也可以通过明确调用Context的unbindService方法与Service解除绑定。当没有任何client与Service绑定的时候,Service会自行销毁(通过startService启动的除外)。
(4). startService和bindService二者执行的回调方法不同:startService启动的服务会涉及Service的onStartCommand回调方法,而通过bindService启动的服务会涉及Service的onBind、onUnbind等回调方法。
使用bindService主要分两种情形:
- Service的调用者与Service在同一个App中;
- Service的调用者是App1中的一个Activity,而Service是App2中的Service,调用者与service分属两个App,这种情形下主要用于实现跨进程的通信。
下面先来看第一种情形,即Service的调用者Activity与Service在同一个App中,该情形也是我们在实际开发中用到最多的情形,因此,第二种情形不再赘述。
下面我们通过一个例子演示一下第一种情形下bindService的基本使用流程。
首先我们有一个MyService,该类继承自Service,在其主要的生命周期回调方法中都加入输出语句。MyService代码如下:
public class MyService extends Service {
public class MyBinder extends Binder{
public MyService getService(){
return MyService.this;
}
}
//通过binder实现调用者Activity与Service之间的通信
private MyBinder binder = new MyBinder();
private final Random generator = new Random();
public void onCreate() {
Log.i("DemoLog","MyService->onCreate");
super.onCreate();
}
public int onStartCommand(Intent intent, int flags, int startId) {
Log.i("DemoLog", "MyService -> onStartCommand, startId: " + startId + ", Thread: " + Thread.currentThread().getName());
return START_NOT_STICKY;
}
public IBinder onBind(Intent intent) {
Log.i("DemoLog", "MyService -> onBind ");
return binder;
}
public boolean onUnbind(Intent intent) {
Log.i("DemoLog", "MyService -> onUnbind");
return false;
}
public void onDestroy() {
Log.i("DemoLog", "MyService -> onDestroy");
super.onDestroy();
}
//getRandomNumber是Service暴露出去供Activity调用的公共方法
public int getRandomNumber(){
return generator.nextInt();
}
}
我们先分析一下上面的代码,调用者Activity要想和Service进行交互,那么Service中需要做什么呢?使用bindService将Activity与Service联系在一起的关键是binder,在MyService中,写了一个内部类MyBinder,该类有个公共方法getService,通过该方法可以获取包含MyBinder的MyService。如果想要自己的Service支持bindService启动方式,就必须在Service的onBind中返回一个IBinder类型的实例。因此实例化了一个MyBinder的实例binder作为MyService的字段,并且将其作为onBind的返回值。也就是说,Service需要完成一下两件主要的事情:
(1).在Service的onBind方法中返回IBinder类型的实例。
(2). onBind方法返回的IBinder的实例需要能够返回Service实例本身或者通过binder暴露出Service公共方法。通常情况下,最简单明了的做法就是将binder弄成Service的内部类,然后在binder中加入类似于getService之类的方法返回包含binder的Service,这样Activity可以通过该方法得到Service实例。
Service创建好以后,我们来创建一个它的调用者Activity,命名为ActivityA,是App的启动界面,代码如下:
public class ActivityA extends Activity implements Button.OnClickListener {
private TestService service = null;
private boolean isBound = false;
private ServiceConnection conn = new ServiceConnection() {
@Override
public void onServiceConnected(ComponentName name, IBinder binder) {
isBound = true;
TestService.MyBinder myBinder = (TestService.MyBinder)binder;
service = myBinder.getService();
Log.i("DemoLog", "ActivityA onServiceConnected");
int num = service.getRandomNumber();
Log.i("DemoLog", "ActivityA中调用MyService的getRandomNumber方法" );
}
public void onServiceDisconnected(ComponentName name) {
isBound = false;
Log.i("DemoLog", "ActivityA onServiceDisconnected");
}
};
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_a);
Log.i("DemoLog", "ActivityA -> onCreate”);
}
public void onClick(View v) {
if(v.getId() == R.id.btnBindService){
//单击了“bindService”按钮
Intent intent = new Intent(this, TestService.class);
intent.putExtra("from", "ActivityA");
Log.i("DemoLog", "-----------------------------");
Log.i("DemoLog", "ActivityA执行bindService");
bindService(intent, conn, BIND_AUTO_CREATE);
}else if(v.getId() == R.id.btnUnbindService){
//单击了“unbindService”按钮
if(isBound){
Log.i("DemoLog", "-----------------------------");
Log.i("DemoLog", "ActivityA执行unbindService");
unbindService(conn);
}
}else if(v.getId() == R.id.btnStartActivityB){
//单击了“start ActivityB”按钮
Intent intent = new Intent(this, ActivityB.class);
Log.i("DemoLog", "-----------------------------");
Log.i("DemoLog", "ActivityA启动ActivityB");
startActivity(intent);
}else if(v.getId() == R.id.btnFinish){
//单击了“Finish”按钮
Log.i("DemoLog", "-----------------------------");
Log.i("DemoLog", "ActivityA执行finish");
this.finish();
}
}
protected void onDestroy() {
super.onDestroy();
Log.i("DemoLog", "ActivityA -> onDestroy");
}
}
通过上面的代码,我们分析一下调用者需要做的工作。代码在调用者ActivityA中初始化了一个ServiceConnection类型的实例,需要重写其onServiceConnected方法以及onServiceDisconnected方法。需要将这个ServiceConnection类型的实例作为参数传给bindService方法,当Service还没有创建的时候,Android会先创建Service的实例,然后执行Service的onBind方法,得到IBinder类型的实例,将该方法作为参数传入ActivityA的ServiceConnection的onServiceConnected方法中,onServiceConnected方法的执行表明ActivityA可以获取到Service的IBinder类型的实例,然后将IBinder转换为自己实际的Binder类型,然后可以通过其直接获取Service的实例或者通过Binder直接执行公共方法,这取决于Service中Binder的具体实现。在本例中,在onServiceConnected方法中,调用者ActivityA通过binder的getService方法获取到了与其对应的Service,然后就可以直接调用Service的公共方法以达到使用Service的目的,这样ActivityA与Service之间就通过IBinder建立了连接,从而进行交互。当ActivityA与Service失去连接时会触发onServiceDisconnected方法。
从上面的分析可以看出,ActivityA需要做到以下几点:
(1).创建ServiceConnection类型的实例,并重写其onServiceConnected方法和onServiceDisconnected方法。
(2).当Android执行onServiceConnected回调方法时,可以通过IBinder实例得到Service的实例对象或直接调用binder的公共方法,这样就实现了ActivityA与Service的连接。
(3).当Android执行onServiceDisconnected回调方法时,表示ActivityA与Service之间断开了连接,因此需要写一些断开连接后需要做的处理。
在知道了如何让ActivityA与Service进行交互之后,来运行这个App,观察各个回调方法的执行过程,另外注意在ActivityA的界面上主要有“bindService”按钮和“unbindService”按钮。
点击ActivityA中的“bindService”按钮,然后点击”unbindService”按钮,输出结果如下所示:
ActivityA -> onCreate
ActivityA执行bindService
MyService->onCreate
MyService -> onBind
ActivityA onServiceConnected
ActivityA中调用MyService的getRandomNumber方法
ActivityA执行unbindService
MyService -> onUnbind
MyService -> onDestroy
通过上面的运行结果可以看到,在调用了bindService之后,由于Service此时还不存在,那么Android就会首先创建一个TestService的实例,并执行其onCreate回调方法,onCreate方法在其生命周期中只会被调用一次。然后会调用Service的onBind方法,该方法只有在第一次bindService调用后才会执行,onBind执行后会返回一个IBinder类型的实例,此时Android会将该IBinder实例存起来,这个IBinder实例是对所有Activity共享的。当下次其他的Activity执行bindService的时候,不会再执行onBind方法,因为之前已经得到了一个IBinder实例,Android会直接使用这个IBinder实例。在得到了IBinder实例之后,Android会执行Activity的ServiceConnection中的onServiceConnected方法,在该方法中会得到IBinder实例,并通过该IBinder实例得到了MyService实例,这样ActivityA就通过IBinder与MyService建立了连接,就可以调用MyService的公共方法,比如调用其getRandomNumber方法获得随机数等。
以上介绍了如何通过绑定的方式启动一个服务。